package br.ufc.customJDBC.routing;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import br.ufc.customJDBC.CustomConnection;
import br.ufc.customJDBC.csv.CSVLog;

class CheckStateRandom extends TimerTask {
	private RandomProvisioningRouter rpr;
	public CheckStateRandom(RandomProvisioningRouter rpr){
		this.rpr = rpr;
	}
	public void run() {
		rpr.checkState();
	}
}

public class RandomProvisioningRouter implements Router{
	
	private static RandomProvisioningRouter singleton = null;

	private Timer timer;
	public static long ms = 1000000;
	public static long sec = 1000000000;

	private List<CustomConnection> fullConnectionPool;
	private List<CustomConnection> connectionPool;
	private int connectionPoolSize;
	private CSVLog csv;

	private List<Long> queriesTime;
	private long sla;
	private long min;
	private int cumulativeSLA;
	private int cumulativeMin;

	private RandomProvisioningRouter(Collection<CustomConnection> connectionPool, long sla, long min, CSVLog csv) {
		this.fullConnectionPool = new ArrayList<CustomConnection>(connectionPool);
		this.connectionPool = fullConnectionPool.subList(0, 1);
		this.connectionPoolSize = this.connectionPool.size();
		queriesTime =  Collections.synchronizedList(new ArrayList<Long>());
		this.sla = sla;
		this.min = min;
		this.cumulativeMin = 0;
		this.cumulativeSLA = 0;
		System.out.println("Random Reactive Provisioning Router:");
		System.out.println("Number of nodes: " + this.connectionPoolSize);
		System.out.println("Connections:");
		for(CustomConnection cc : this.connectionPool){
			System.out.println(cc.getIp());
		}
		
		startTimer();
		this.csv = csv;
	}
	
	private void startTimer(){
		this.timer = new Timer();
		timer.scheduleAtFixedRate(new CheckStateRandom(this), 10000, 10000);
		queriesTime.clear();
	}
	private void stopTimer(){
		timer.cancel();
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static RandomProvisioningRouter getInstance(Collection<CustomConnection> connectionPool, long sla, long min, CSVLog csv){
		if(singleton == null){
			singleton = new RandomProvisioningRouter(connectionPool, sla, min, csv);
			return singleton;
		}else{
			return singleton;
		}
	}

	private CustomConnection nextConnection(){
		Random rand = new Random();
		int randomNum = rand.nextInt(connectionPoolSize);
		return connectionPool.get(randomNum);
	}

	private double avgList(List<Long> l){
		long sum = 0;
		for (long lo : l){
			sum = sum + lo;
		}

		System.out.println("l.size(): " + l.size());
		return ((double)sum)/((double)l.size());
	}

	public void checkState(){
		double avg;
		System.out.println();
		synchronized(queriesTime){
			avg = avgList(queriesTime);
			queriesTime.clear();	
		}		
		if(avg > sla){
			cumulativeSLA ++;
			cumulativeMin = 0;
			if (cumulativeSLA >= 2){
				stopTimer();
				addResource();
				startTimer();
				cumulativeSLA = 0;
			}
		}else if(avg < min){
			cumulativeMin ++;
			cumulativeSLA = 0;
			if(cumulativeMin >= 2){
				stopTimer();
				removeResource();
				startTimer();
				cumulativeMin = 0;
			}
		}else{
			cumulativeMin = 0;
			cumulativeSLA = 0;
		}
		System.out.println("Avg: " + (long)avg);
		System.out.println("SLA: " + sla);
		System.out.println("Min: " + min);
		System.out.println("cumulativeSLA: " + cumulativeSLA);
		System.out.println("cumulativeMin: " + cumulativeMin);
		System.out.println("connectionPoolSize: " + connectionPoolSize);
		
	}

	private void addResource(){
		System.out.println("addResource");
		if(connectionPoolSize < fullConnectionPool.size()){
			connectionPool = fullConnectionPool.subList(0, connectionPoolSize + 1);
			connectionPoolSize = connectionPool.size();
		}		
	}

	private void removeResource(){
		System.out.println("removeResource");
		if(connectionPoolSize > 1){
			connectionPool = fullConnectionPool.subList(0, connectionPoolSize - 1);
			connectionPoolSize = connectionPool.size();
		}		
	}

	@Override
	public ResultSet executeQuery(String sql) throws SQLException {
		CustomConnection con = nextConnection();
		Statement statement = con.createStatement();

		long startTime = System.nanoTime();
		ResultSet ret = statement.executeQuery(sql);
		long stopTime = System.nanoTime();
		
		long rt = stopTime - startTime;
		queriesTime.add(rt);

		String nodes = "[ " + con.getIp() + " ]";
		csv.writeLine(sql, rt, 1, "executeQuery", nodes, connectionPoolSize );
		//		statement.close();
		return ret;
	}
	
	@Override
	public boolean execute(String sql) throws SQLException {

		CustomConnection con = nextConnection();
		Statement statement = con.createStatement();

		long startTime = System.nanoTime();
		boolean ret = statement.execute(sql);
		long stopTime = System.nanoTime();


		String nodes = "[ " + con.getIp() + " ]";
		csv.writeLine(sql, stopTime - startTime, 1, "execute", nodes, connectionPoolSize );
		//		statement.close();
		return ret;
	}

	@Override
	public int executeUpdate(String sql) throws SQLException {
		CustomConnection con = nextConnection();
		Statement statement = con.createStatement();

		long startTime = System.nanoTime();
		int ret = statement.executeUpdate(sql);
		long stopTime = System.nanoTime();

		String nodes = "[ " + con.getIp() + " ]";
		csv.writeLine(sql, (stopTime - startTime), 1, "executeUpdate", nodes, connectionPoolSize );

		//		statement.close();
		return ret;
	}
}

